package com.db.stockexchange.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Logger;

import com.db.stockexchange.common.messages.FIXMessage;
import com.db.stockexchange.common.parsing.FIXParseException;
import com.db.stockexchange.common.parsing.FIXParser;

public class Connection implements Runnable {
	static private final String IN = " IN";
	static private final String OUT = "OUT";
	static private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
	
	private Socket socket;
	BufferedReader reader;
	PrintWriter writer;
	FIXParser parser;
	MessageHandler handler;
	Logger logger;
	
	public Connection(InetAddress address, int port, MessageHandler handler) throws IOException {
		logger = Logger.getLogger(this.getClass().getName());
		try {
			socket = new Socket(address, port);
			logger.fine(String.format("Connect to %s:%d", address.toString(), port));
			
			writer = new PrintWriter(socket.getOutputStream(), true);
			reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		} catch (IOException e) {
			logger.warning("Exception occured while opening client socket: " + e.getMessage());
			close();
			throw e;
		}

		parser = new FIXParser();
		this.handler = handler;
	}
	
	@Override
	public void run() {
		while_loop:
		while (true) {
			try {
				String s = reader.readLine();
				if (s != null && !s.isEmpty()) {
					FIXMessage message = parser.parseMessage(s);

					logger.info(String.format("%s: %s: %s", IN, dateFormat.format(new Date()), message));
					
					switch (message.getType()) {
					case LOGON:
					case EXEC_REPORT:
					case REJECT:
						handler.processMessage(message);
						break;
					default:
						logger.warning(String.format("Received message has unsupported type <%s>", message.getType()));
					}
				}
			} catch (SocketException e) {
				break while_loop;
			}
			catch (IOException e) {
				if (socket.isClosed())
					break while_loop;
				logger.warning(e.getClass() + " occurred while reading client message: " + e.getMessage());
			} catch (FIXParseException e) {
				logger.warning("FIXParesException occurred while parsing received message: " + e.getRejectedMessage().getReasonDetail());
			}
		}
		
		close();
	}

	public void sendMessage(final FIXMessage message) {
		logger.info(String.format("%s: %s: %s", OUT, dateFormat.format(new Date()), message));
		writer.println(parser.representMessage(message));
	}
	
	public FIXMessage sendSynchronousMessage(FIXMessage message) {
		logger.info(String.format("%s: %s: %s", OUT, new Date().toString(), message));
		writer.println(parser.representMessage(message));

		FIXMessage response = null;
		try {
			String s = reader.readLine();
			if (s != null && !s.isEmpty()) {
				response = parser.parseMessage(s);
				
				switch (message.getType()) {
				case LOGON:
				case EXEC_REPORT:
				case REJECT:
					break;
				default:
					logger.warning(String.format("Received message has unsupported type <%s>", message.getType()));
				}
			}
		}
		catch (IOException e) {
			logger.warning(e.getClass() + " occured while reading client message: " + e.getMessage());
		} catch (FIXParseException e) {
			logger.warning("FIXParesException occured while parsing received message: " + e.getRejectedMessage().getReasonDetail());
		}
		
		return response;
	}
	
	public void close() {
		try {
			if (socket != null)
				socket.close();
			if (reader != null)
				reader.close();
			if (writer != null)
				writer.close();	
		} catch (IOException ex) {
			logger.warning("Exception occured while closing client socket: " + ex.getMessage());
		}
	}
}
